package server;

import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import common.PhaseEnum;
import common.Player;
import common.Round;
import common.Sendable;

public class NetworkMediator {

	private final List< Client > allClients;
	private final ConcurrentHashMap< Player, Client > regClients;
	private final Selector selector;
	private final ScheduledExecutorService service;
	
	public NetworkMediator( Selector sel, List<Client> allClients, ConcurrentHashMap< Player, Client > registeredClients, ScheduledExecutorService service ) {
		this.selector = sel;
		this.regClients = registeredClients;
		this.allClients = allClients;
		this.service = service;
	}
	
	public boolean deregister( Player p ) {
		Client c = regClients.remove( p );
		
		if( c != null )
			c.player.setRound(null);
		
		return c != null;
	}
	
	public void sendTo( Client c, Sendable s ) throws ClosedChannelException {
		write( c, s);
	}
	
	public void sendTo( Player p, Sendable s ) throws ClosedChannelException {
		//Get on a concurrent hashMap.
		Client c = regClients.get( p );
		write( c, s);
	}
	
	//Safe to use only during the Trading or Results phase of the round
	public void sendToAllInRound( Round r, Sendable s ) throws ClosedChannelException {
		List<Player> players = r.getPlayers();
		
		if( r.getCurrentPhase() == PhaseEnum.REGISTRATION ) {
			synchronized( players ) {
				for( Player p : r.getPlayers() ) {
					Client c = regClients.get( p );
					write( c, s );
				}
			}
		} else {
			//The player list is considered final in results or trading so no need to synchronize.
			for( Player p : r.getPlayers() ) {
				Client c = regClients.get( p );
				write( c, s );
			}
		}
	}
	
	public void sendToAllClients( Sendable s ) throws ClosedChannelException {
		synchronized( allClients ) {
			for( Client c : allClients )
				write( c , s );
		}
		
	}
	
	private void write( Client c, Sendable s ) throws ClosedChannelException {
		if( c == null )
			return;
		
		
		
//		System.out.println("NetMediator: Writing " + s.getType() );
		
		synchronized( c.output ) {
			int prev = c.pendingWrites.getAndIncrement();
			
			c.output.add(s);
			
			if( prev == 0 ) {
				SelectionKey key = c.destination.keyFor(selector); 
				
				//This can happen if the key was canceled and the selector removes it from its keySet... I think.
				if( key == null ) 
					return;
				
				try {
					key.interestOps( SelectionKey.OP_WRITE );
//					System.out.println("NetMediator: interest set to Writing!");
					selector.wakeup();
				} catch( CancelledKeyException e ) {
					//If the key was canceled and not removed from the selector's keySet it can throw this exception.
					//Just absorb it and move on.
				}
			}
		}
		

		

	}
	
	public ScheduledFuture<?> schedule( Runnable r, long delay, TimeUnit unit ) {
		return service.schedule(r, delay, unit);
	}
	
	public ScheduledFuture<?> scheduleAtFixedRate( Runnable r, long delay, long period, TimeUnit unit ) {
		return service.scheduleAtFixedRate(r, delay, period, unit );
	}
	
}
